有赞移动权限体系建设
点击关注“有赞coder”
获取更多技术干货哦~
部门:零售移动
一、背景
权限管理是一个几乎所有大中型 B 端系统都会涉及的重要组成部分,其目的是对整个系统进行权限控制,避免造成误操作及数据泄露等风险问题。在充分调研了商家的经营需求后,传统的老板、店长、收银员等角色不足以覆盖商家角色场景。因此,在原有权限系统的基础上,增加了商家自主定义员工权限的能力,满足其细粒度管控员工权限的诉求。任何一家使用有赞开店软件经营店铺的商家,不仅能给员工赋予默认的角色,也可以实时给员工开放特定的权限、修改权限,以此保障店铺运营的安全、健康。
二、权限与权限管理
2.1 名词定义
在讲述有赞使用的权限模型之前,先介绍一下权限相关的基本概念:
权限:用户可操作行为的最小单位。
用户:每个用户都有唯一标识,并被授予一个或多个角色。
角色:由不同的权限组合而成,最终分配给具体用户。
权限管理:控制用户的权限,只能访问授权内容。
2.2 模型选择
ACL(Access Control List):基于用户级别的权限控制。
将系统的各种权限直接授予具体的用户。抽象来说,为每个用户维护了单独的权限列表,当需要分配权限、收回权限时,需要修改对应用户的权限信息。
RBAC(Role Base Access Control):基于角色级别的权限控制。
与 ACL 对比,RBAC不用给用户单个分配权限,权限与用户之前通过角色关联。通过给不同的角色分配不同的权限,只需要将用户指向对应的角色就会有对应的权限。分配权限、收回权限只需要通过修改用户的角色即可。
ABAC(Attribute Base Access Control):基于属性级别的权限控制。
不同于常见的将用户通过某种方式直接关联到权限的方式,ABAC 是通过动态计算一个或一组属性来是否满足某种条件来进行权限判断。
属性一般分为四类:用户属性(自然人属性,如年龄、性别等),环境属性(物理环境,如时间、地点、气候),操作属性(读、写)和对象属性(操作对象,如资金、某张图片、某个特定的页面,又称资源属性)。
因此理论上能够实现灵活的权限控制、将在权限与用户之前通过一组或多组属性实现关联,几乎能满足所有类型的需求。
基于线下经营的物理场景,有赞需要研发一套更灵活的权限管理系统,能将商家的权限需求,具象为多个不同的、支持商家自由勾选、定制的角色。从灵活性层面看,不需要对每个员工逐一做个性化定制,只需要对某一类员工做权限个性化定制(包含默认权限授权)。在权衡了权限的灵活性需求、管理维护难度、性能瓶颈之后,有赞最终选择RBAC 模型研发权限管理系统。
2.3 权限管控
抽象来看权限体系可以分为如下两类:功能权限与数据权限两部分。
功能权限指的是在系统中的功能可否使用,通常我们将功能权限分为查看、编辑、删除等,同时编辑、删除权限又包含了查看。通过小的权限点拆分更精细的赋予了员工能否进入某个页面查看信息、编辑信息的能力。
数据权限指数据中存在的数据是否能查看,是一个更细粒度的权限。比如一个页面,不同角色查看不同的数据就需要通过数据权限控制。
从管理对象维度又可以分为:店铺能力 与 员工能力。
店铺能力店铺维度的权限,比如有赞的商业化插件,可以通过店铺能力去体现。
员工能力赋予员工的权限,比如收银开单、资金管理等。
店铺能力优先级绝对高于员工能力,所有场景的权限判断,店铺能力必须先于员工能力。简单地说,店铺能力决定了“店铺能做什么”,员工能力决定了“用户能做什么”。
三. 权限系统1.0(SAM)
SAM平台是之前在使用的权限系统,使用的权限模型是 RBAC 模型。它的核心思路是将所有的权限结果抽象成一个 64 位的 long 型。使用方在查询某个权限点时,需将权限点与后端返回的权限集做一个位运算。
后端返回的权限数据如图所示:
//权限点
{
"menuId": 113101101,
"menuName": "网店查看",
"mapBizPerms": {
"retail": [0, 0, 1125899906842624]
}
}
//员工个人能力
{
"retail": [4611686018427387903, -144115188075855873, -3]
}
配置难: 权限需求实现频繁依赖后端资源。在日常开发中,如果要新增权限,首先需要业务方提供一个权限需求,而权限需求有由权限描述、角色类型、店铺类型组成,后端资源到位后,实现该需求、发布至相应的测试环境,同时将权限信息同步至端侧同学,端侧同学继续完成接下来的代码适配。这条链路流程多、沟通成本高、资源依赖重、排队耗时长。 排查难:原来的设计,导致端上也存在权限计算,且计算方式依赖位运算,无法直接定位问题所在。在排查权限问题时,业务往往需要先进行复杂的计算,当问题定位在权限数据出错时,需要再次依赖后端资源,进行数据的修正和发布。 扩展难:权限属于中台应用,承接了微商城、零售、教育等业务方,终端类型有pc、移动端 APP、小程序、deskTop 等。SAM 系统提供的能力比较单一,无法为各端提供可定制能力。原有的菜单功能是参考 pc 设计的,无法满足移动端页面级别的的多类型组件需求。
四.机遇与挑战
业务语言“说人话”:权限应当隔离业务语言与实现语言,对接方不再需要理解晦涩的权限业务语言,“权限由业务定义”。 配置方式由原来的“工具”向“操作平台”改变:权限由原来的“开发工具”平台化,并向运营平台转变,将单侧的资源压力平衡到需求方。 端侧升级架构,提供更丰富的能力:提供权限解析能力,解放业务在权限判断中的生产力。
五.权限系统2.0
rig 系统(Rig:北欧神话中彩虹桥的守护神):添加了权限表达层,以相对友好的方式呈现给业务方,替换以往将实现逻辑直接暴露给业务方的方式。 权限配置平台:权限配置平台支持业务域配置不同店铺类型的权限,支持各端根据店铺类型、平台配置不同的菜单。需求方不再依赖后端资源,开发、产品或运营可直接根据权限需求、基于店铺类型、角色、不同的平台配置权限、菜单。 dynamicMenu:移动端组件动态化方案。需求方在配置平台增删、编辑APP的工作台菜单后,菜单内所有的组件经过权限校验后下发,且兼容零售移动的特殊业务模式(如离线模式)。
权限后台
菜单服务:获取渲染菜单。 API 服务:API 校验。 角色服务:获取角色、同步角色、增删角色等。 用户服务:用户新增、更新、删除、
权限中台
移动端
工作台架构
JS Engine:JS 实现的跨端计算引擎,提供统一的组件解析、权限和店铺能力解析 、移动端扩展能力解析。 WigetManager:负责管理移动端所有的 widget 管理、路由分发、widget 形态预处理。 UI 组件层:提供基础的组件如表单、网格、按钮等。 UI 展示层:业务根据配置,纵向展示对应的组件。
工作台实现
@Nav("dynamicMenuWidget/UrgentNotice")
class UrgentNoticeWidgetFragment : BaseRetailWidgetFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.home_widget_urgent_notice, container, false)
}
//数据获取成功回调
override fun loadSuccess(data: UrgentNoticeResponse?) {}
//数据获取失败回调
override fun loadError(t: Throwable) {}
//widget点击事件
override fun onWidgetClicked() {}
}
val fragment = Navigator.newInstallOrNull<BaseWidgetFragment>("dynamicMenuWidget/UrgentNotice")
class WidgetDelegate{
val widgetMap:HashMap<String,BaseWidgetFragment>
val activity: Activity
var rootWidgetId: String = ""
val children = ArrayList<WidgetDelegate>()
var parentDelegate: WidgetDelegate? = null
constructor(activity: Activity, rootWidgetId: String, parentWidgetDelegate: WidgetDelegate? = null
) {}
constructor(fragment: BaseWidgetFragment) {
/**
* 加载widget
* containerId:容器id
* doAction: 业务方自定义处理数据
*/
fun setupUI(fragmentManager: FragmentManager,@IdRes containerId: Int, beforeAction: (List<WidgetInfo>.() -> List<WidgetInfo>) = { this }) {}
/**
* widget刷新,用于下拉刷新等
*/
fun reload() {}
/**
* 通过widgetId获取widget
*/
fun findWidgetById(widgetId: String): BaseWidgetFragment? {}
}
//通过menuId注册点击事件
DynamicMenuGlabalListenerManager.addOnWidgetClickedListener("widget_finc_stock_search"){context,widgetInfo ->
service.query(){response->
if(response.success){
startActivity()
}else{
showToast(response.msg)
}
}
}
将组件测试与权限解耦,测试不再需要回归权限相关的用例。 业务组件不需要鉴权,JSEngine 进行了严密的权限校验,没有数据越权风险。 除了支持权限相关的所有配置、还支持版本控制、组件的随时上下架。 移动端不同平台机型上,保证了内容的完全一致性。
六.价值回顾
七.结语
拓展阅读:
有赞移动 iOS 组件化(模块化)架构设计实践 有赞零售移动端收银商品实践 聊聊UI标准化 有赞零售 App 离线切换技术方案 有赞 Android 编译进阶之路——全量编译提效方案 有赞iOS精准测试实践 有赞 Android 编译进阶之路 —— 增量编译提效方案Savitar 有赞iOS-基于二进制的编译提效策略
Vol.372